package org.orbroker.util

import java.lang.reflect._

private[orbroker] object Mirror {

  private def nodeValue(nodeObj: AnyRef, nodeName: String): Any = {
    var parmObj = unOption(nodeObj)
    var parmName = nodeName
    if (parmName endsWith "]") {
      val pos = parmName lastIndexOf '['
      if (pos + 2 == parmName.length) throw new IllegalArgumentException("Index empty: " + nodeName)
      val idx = Integer.parseInt(parmName.substring(pos + 1, parmName.length - 1))
      parmName = parmName.substring(0, pos)
      parmObj = nodeValue(parmObj, parmName).asInstanceOf[AnyRef]
      indexObject(parmObj, idx)
    } else if (parmObj.isInstanceOf[java.util.Map[_, _]]) {
      val map = parmObj.asInstanceOf[java.util.Map[String, _]]
      map.get(parmName)
    } else if (parmObj.isInstanceOf[scala.collection.Map[_, _]]) {
      val map = parmObj.asInstanceOf[scala.collection.Map[String, _]]
      map(parmName)
    } else {

    // TODO: Add property check here too

      val method = parmObj.getClass.getMethod(parmName)
      method.invoke(parmObj)
    }
  }

  private def indexObject(nodeObj: AnyRef, idx: Int): Any = {
    var obj = unOption(nodeObj) match {
      case list: java.util.List[_] ⇒ list.get(idx)
      case seq: Seq[_] ⇒ seq(idx)
      case a: Any if (a.getClass.isArray) ⇒ java.lang.reflect.Array.get(a, idx)
      case _ ⇒ throw new IllegalArgumentException
    }
    obj
  }

  private def unOption(obj: Any): AnyRef = obj match {
    case Some(o: AnyRef) ⇒ o
    case None ⇒ null
    case null ⇒ null
    case o: AnyRef ⇒ o
  }

  def leafValue(rootObj: AnyRef, nodePath: String): Any = {
    if (rootObj == null || rootObj == None) {
      null
    } else if (!nodePath.contains(".")) {
      unOption(nodeValue(rootObj, nodePath))
    } else {
      var node: Any = unOption(rootObj)
      val tokens = new java.util.StringTokenizer(nodePath, ".")
      while (tokens.hasMoreTokens && node.isInstanceOf[AnyRef]) {
        node = unOption(nodeValue(node.asInstanceOf[AnyRef], tokens.nextToken))
      }
      node
    }

  }

  private def findStaticInstance[T](cls: Class[T]): Option[T] = {
    var instance: Option[T] = None
    for (field ← cls.getFields)
      if (field.getType == cls && Modifier.isStatic(field.getModifiers))
        instance = Some(field.get(null).asInstanceOf[T])
    instance
  }

  def getInstance[T](cls: Class[T]): T = {
    findStaticInstance(cls) match {
      case Some(i) ⇒ return i
      case None ⇒ // Continue below
    }
    val clsName = cls.getName
    if (!clsName.endsWith("$"))
      findStaticInstance(Class.forName(clsName concat "$")) match {
        case Some(i) ⇒ return i.asInstanceOf[T]
        case None ⇒
      }
    throw new org.orbroker.exception.ConfigurationException("Cannot get instance of " + clsName)
  }
}
